package org.qtmo.emu.nes;
import flash.display.Shape;
import flash.events.Event;

/**
 * ...
 * @author Sang Hua Cai
 */
class NES
{
	public var cpu:CPU;
	public var apu:APU;
	public var ppu:PPU;
	public var rom:ROM;
	public var mmu:MMU;

	public function new()
	{
		Running = false;

		mmu = new MMU(this);
		mmu.Initialize();

		cpu = new CPU(this);
		apu = new APU(this);
		ppu = new PPU(this);
		rom = new ROM(this);
		
		new FrameTrigger(EmulateFrame);
	}

	public function Reset():Void
	{
		cpu.Reset();
		//ppu.Reset();
		//apu.Reset();
		rom.mapper.Reset();

		BASE_cycles = 0;
		EMULATED_cycles = 0;

		Running = true;
		
		EmulateFrame();
	}

	private function EmulateFrame():Void
	{
		trace("frame start");
		if ( !Running )
		{
			return;
		}
		
		var	scanline:UInt = 0;

		while ( true )
		{
			ppu.SetRenderScanline( scanline );

			if ( scanline == 0 )
			{
				EmulateCPU( HDRAW_CYCLES );
				ppu.FrameStart();
				ppu.ScanlineNext();
				//TO-DO
				//mapper->HSync( scanline );
				EmulateCPU( FETCH_CYCLES*32 );
				ppu.ScanlineStart();
				EmulateCPU( FETCH_CYCLES*10 + END_CYCLES );
			}
			else if( scanline < 240 )
			{
				ppu.Scanline( scanline );
				EmulateCPU( HDRAW_CYCLES );
				ppu.ScanlineNext();
				//TO-DO
				//mapper->HSync( scanline );
				EmulateCPU( FETCH_CYCLES*32 );
				ppu.ScanlineStart();
				EmulateCPU( FETCH_CYCLES*10 + END_CYCLES );

			}
			else if ( scanline == 240 )
			{
				//TO-DO
				//mapper->VSync();
				EmulateCPU( HDRAW_CYCLES );
				//TO-DO
				//mapper->HSync( scanline );
				EmulateCPU( HBLANK_CYCLES );

			}
			else if ( scanline <= TOTAL_SCANLINE-1 )
			{
				// VBLANK
				if ( scanline == TOTAL_SCANLINE-1 )
				{
					ppu.VBlankEnd();
				}

				if ( scanline == 241 )
				{
					ppu.VBlankStart();
					if ( PPU.GetPPUREG(0) & PPU.PPU_VBLANK_BIT > 0 )
					{
						cpu.NMI();
					}
				}
				EmulateCPU( HDRAW_CYCLES );
				//TO-DO
				//mapper->HSync( scanline );
				EmulateCPU( HBLANK_CYCLES );

				if ( scanline == TOTAL_SCANLINE - 1 )
				{
					break;
				}
			}
			scanline++;
		}
	}

	private function EmulateCPU( baseCycles:UInt ):Void
	{
		BASE_cycles += baseCycles;
		var cycles:UInt = Math.floor((BASE_cycles / 12) - EMULATED_cycles);
		if ( cycles > 0)
		{
			EMULATED_cycles += cpu.EXEC( cycles );
		}
	}

	private var Running:Bool;
	private var BASE_cycles:UInt;
	private var EMULATED_cycles:UInt;

	private static inline var BASE_CLOCK:UInt				= 26601714;		// Base clock
	private static inline var CPU_CLOCK:UInt 				= 1662607;		// Cpu clock
	private static inline var TOTAL_SCANLINE_CYCLES:UInt 	= 1278;			// Scanline total cycles(15.625KHz)
	private static inline var TOTAL_SCANLINE:UInt 			= 312;			// Total scanlines
	private static inline var HDRAW_CYCLES:UInt				= 960;			// H-Draw cycles
	private static inline var HBLANK_CYCLES:UInt 			= 318;			// H-Blank cycles
	private static inline var END_CYCLES:UInt				= 2;			// End cycles
	private static inline var FRAME_CYCLES:UInt 			= 1278 * 312;	// Frame cycles
	private static inline var FRAME_IRQ_CYCLES:UInt			= 33252;		// FrameIRQ cycles
	private static inline var FRAME_RATE:UInt 				= 50;			// Frame rate(Hz)
	private static inline var FRAME_PERIOD:UInt				= 20;			// Frame period(ms)
	private static inline var FETCH_CYCLES:UInt				= 8;

}
class FrameTrigger extends Shape
{
	public function new( Func:Void -> Void )
	{
		super();
		
		func = Func;		
		this.addEventListener(Event.ENTER_FRAME, OnFrame);
	}
	
	private function OnFrame( event:Event ):Void
	{
		func();
	}
	
	private var func:Void->Void;
}
